import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import org.omg.CORBA.FREE_MEM;

public class Serveur implements Serializable {

    static private final long serialVersionUID = 6L;
    static public String DEFAULT_PATH_SERVEUR = "serveur";
    static public String DEFAULT_PATH_TRANSPORTS = "transports";
    private List<Transport> transports;

    public Serveur() {
        transports = new ArrayList<Transport>();
    }

    public Serveur(Serveur s) {
        transports = s.getTransports();
    }

    /**
     * Save the server state into a binary file
     */
    public void saveServerAsDataFile() {

        try {
            FileOutputStream fos = new FileOutputStream(DEFAULT_PATH_SERVEUR);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            try {
                oos.writeObject(this);
                oos.flush();
                System.out.println("Serialization OK");
            } finally {
                try {
                    oos.close();
                } finally {
                    fos.close();
                }
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }

    /**
     * Load the server state
     */
    public static Serveur loadServerWithDataFile(String path) {

        Serveur s = null;
        try {
            FileInputStream fis = new FileInputStream(path);
            ObjectInputStream ois = new ObjectInputStream(fis);
            try {
                s = (Serveur) ois.readObject();
            } finally {
                try {
                    ois.close();
                } finally {
                    fis.close();
                }
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        } catch (ClassNotFoundException cnfe) {
            cnfe.printStackTrace();
        }
        if (s != null) {
            System.out.println(s + " a été déserialisé avec succès\n");
        }
        return s;
    }
    
    /**
     * Load transports 
     */
    public static List<Transport> loadTransportsWithDataFile() {

        List<Transport> res = null;
        try {
            FileInputStream fis = new FileInputStream(DEFAULT_PATH_TRANSPORTS);
            ObjectInputStream ois = new ObjectInputStream(fis);
            try {
                res = (List<Transport>) ois.readObject();
            } finally {
                try {
                    ois.close();
                } finally {
                    fis.close();
                }
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        } catch (ClassNotFoundException cnfe) {
            cnfe.printStackTrace();
        }
        if (res != null) {
            System.out.println("Transports" + " a ete deserialise");
        }
        return res;
    }

    /**
     * Save as *.txt file
     */
    public void saveServerAsTextFile() {
        SerializeText.saveServeur(this);
    }
    
    /**
     * Save transports as *.txt file
     */
    public void saveTransportsAsTextFile() {
        try {
            SerializeText.saveTransports(this.getTransports());
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }
    
    /**
     * Save transports into a binary file
     */
    public void saveTransportsAsDataFile() {
        try {
            FileOutputStream fos = new FileOutputStream(DEFAULT_PATH_TRANSPORTS);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            try {
                oos.writeObject(this.transports);
                oos.flush();
                System.out.println("Serialization Transports OK");
            } finally {
                try {
                    oos.close();
                } finally {
                    fos.close();
                }
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }

    }


    /**
     * 
     * @return a list containing the transports
     */
    public static List<Transport> loadTransportsWithTextFile() {
        try {
            return SerializeText.loadTransport();
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return null;
        }
    }
    
    /**
     * @return the server
     */
    public static Serveur loadServerWithTextFile() {
        
        return SerializeText.loadServeur();
    }
    
    /**
     * Load/Update the list of transports
     */
    public void loadTransports(List<Transport> l) {
        transports = l;
    }

    /**
     * See all the transports
     */
    public void consult() {
        System.out.println("LISTE DES TRANSPORTS\n");
        for (Transport entry : transports) {
            System.out.println(entry.toString());
            System.out
                    .println("--------------------------------------------------------");
        }
    }

    /**
     * Ajouter un transport
     */
    public void addTransport(Transport t) {
        this.transports.add(t);
    }

    /**
     * Modifier un transport
     */
    public boolean modifyTransport(int idTransport, TypeTransport type,
            int capacity) {
        try {
            Transport t = getTransport(idTransport);
            if (type != null)
                t.setType(type);
            t.setCapacity(capacity);
            return true;
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return false;
        }

    }

    /**
     * Get the list of Transport
     */
    public List<Transport> getTransports() {
        return transports;
    }

    public String toString() {
        String s = "Serial : (" + serialVersionUID + ") ";
        return s;
    }

    /**
     * Effectuer une recherche de Transport faisant un trajet donné à partir
     * d'une date donnée
     * 
     * @param start
     * @param arrived
     * @param date
     * @return HasMap
     */
    private HashMap<Transport, Trajet> searchTransport(Ville start,
            Ville arrived, Date date) {
        HashMap<Transport, Trajet> result = new HashMap<Transport, Trajet>();
        for (Transport transport : this.transports) {
            for (Trajet trajet : transport.getJourney()) {
                if (trajet.getStart().name().equals(start.name())
                        && trajet.getArrived().name().equals(arrived.name())
                        && trajet.getFreeCapacity() > 0
                        && date.getYear() == trajet.getStartDate().getYear()
                        && date.getDay() == trajet.getStartDate().getDay()
                        && date.getHours() <= trajet.getStartDate().getHours()) {

                    result.put(transport, trajet);
                }
            }
        }

        return result;
    }
    
    /**
     * Effectuer une recherche de Transport faisant un trajet donné à partir
     * d'une date donnée
     * 
     * @param start
     * @param arrived
     * @param date
     * @return HasMap
     */
    private HashMap<Transport, Trajet> searchTransport(Ville start,
            Ville arrived) {
        HashMap<Transport, Trajet> result = new HashMap<Transport, Trajet>();
        for (Transport transport : this.transports) {
            for (Trajet trajet : transport.getJourney()) {
                if (trajet.getStart().name().equals(start.name())
                        && trajet.getArrived().name().equals(arrived.name())
                        && trajet.getFreeCapacity() > 0) {

                    result.put(transport, trajet);
                }
            }
        }

        return result;
    }

    /**
     * Effectuer une recherche de transport partant d'une ville donné et à
     * partir d'une date donnée
     * 
     * @param start
     * @param date
     * @return HashMap
     */
    private HashMap<Transport, Trajet> searchTransport(Ville start, Date date) {
        HashMap<Transport, Trajet> result = new HashMap<Transport, Trajet>();
        for (Transport transport : this.transports) {
            for (Trajet trajet : transport.getJourney()) {
                if (trajet.getStart().name().equals(start.name())
                        && trajet.getFreeCapacity() > 0
                        && date.getYear() == trajet.getStartDate().getYear()
                        && date.getDay() == trajet.getStartDate().getDay()
                        && date.getHours() <= trajet.getStartDate().getHours()) {

                    result.put(transport, trajet);
                }
            }
        }

        return result;
    }

    /**
     * Effectuer une recherche des transport partant d'une ville donnée
     * 
     * @param start
     * @return HashMap
     */
    private HashMap<Transport, Trajet> searchTransportWithStartedCity(Ville start) {
        HashMap<Transport, Trajet> result = new HashMap<Transport, Trajet>();
        for (Transport transport : this.transports) {
            for (Trajet trajet : transport.getJourney()) {
                if (trajet.getStart().name().equals(start.name())
                        && trajet.getFreeCapacity() > 0) {

                    result.put(transport, trajet);
                }
            }
        }

        return result;
    }
    
    /**
     * Effectuer une recherche des transport allant vers une ville donnée
     * 
     * @param arrived
     * @return HashMap
     */
    private HashMap<Transport, Trajet> searchTransportWithArrivedCity(Ville arrived) {
        HashMap<Transport, Trajet> result = new HashMap<Transport, Trajet>();
        for (Transport transport : this.transports) {
            for (Trajet trajet : transport.getJourney()) {
                if (trajet.getArrived().name().equals(arrived.name())
                        && trajet.getFreeCapacity() > 0) {

                    result.put(transport, trajet);
                }
            }
        }

        return result;
    }

    /**
     * Obtenir l'ensembe des transports et trajet d'un voyageur
     */
    private HashMap<Transport, Trajet> searchTransport(Voyageur v) {
        HashMap<Transport, Trajet> result = new HashMap<Transport, Trajet>();
        for (Transport transport : this.transports) {
            for (Trajet trajet : transport.getJourney()) {
                if (trajet.getReservations().contains(v)) {
                    result.put(transport, trajet);
                }
            }
        }

        return result;
    }

    /**
     * Effectuer et afficher une recherche de Transport faisant un trajet donné
     * à partir d'une date donnée
     * 
     * @param start
     * @param arrived
     * @param date
     */
    public void search(Ville start, Ville arrived, Date date) {
        HashMap<Transport, Trajet> result = searchTransport(start, arrived,
                date);
        String resultString = new String();

        for (Transport transport : result.keySet()) {
            for (Trajet trajet : transport.getJourney()) {
                if (trajet.getStart().name().equals(start.name())
                        && trajet.getArrived().name().equals(arrived.name())
                        && trajet.getFreeCapacity() > 0
                        && date.getYear() == trajet.getStartDate().getYear()
                        && date.getDay() == trajet.getStartDate().getDay()
                        && date.getHours() <= trajet.getStartDate().getHours()) {
                    resultString += "ID : " + transport.getIdTransport() + "\nType : "
                    + transport.getType() + "\nTrajets :\n\t"
                    + trajet.toString() + "\n\t";
                }
            }
        }

        System.out.println(resultString);
    }
    
    /**
     * Effectuer et afficher une recherche de Transport faisant un trajet donné
     * 
     * @param start
     * @param arrived
     */
    public void search(Ville start, Ville arrived) {
        HashMap<Transport, Trajet> result = searchTransport(start, arrived);
        String resultString = new String();
        
        for (Transport transport : result.keySet()) {
            for (Trajet trajet : transport.getJourney()) {
                if (trajet.getStart().name().equals(start.name())
                        && trajet.getArrived().name().equals(arrived.name())) {
                    resultString += "ID : " + transport.getIdTransport() + "\nType : "
                    + transport.getType() + "\nTrajets :\n\t"
                    + trajet.toString() + "\n\t";
                }
            }
        }
        
        System.out.println(resultString);
    }

    /**
     * Effectuer et afficher une recherche de transport partant d'une ville
     * donné et à partir d'une date donnée
     * 
     * @param start
     * @param date
     */
    public void search(Ville start, Date date) {
        HashMap<Transport, Trajet> result = searchTransport(start, date);
        String resultString = new String();
        
        for (Transport transport : result.keySet()) {
            for (Trajet trajet : transport.getJourney()) {
                if (trajet.getStart().name().equals(start.name())
                        && trajet.getFreeCapacity() > 0
                        && date.getYear() == trajet.getStartDate().getYear()
                        && date.getDay() == trajet.getStartDate().getDay()
                        && date.getHours() <= trajet.getStartDate().getHours()) {
                    resultString += "ID : " + transport.getIdTransport() + "\nType : "
                    + transport.getType() + "\nTrajets :\n\t"
                    + trajet.toString() + "\n\t";
                }
            }
        }
        
        System.out.println(resultString);
    }

    /**
     * Effectuer et afficher une recherche des transports partant d'une ville
     * donnée
     * 
     * @param start
     */
    public void searchStart(Ville start) {
        HashMap<Transport, Trajet> result = searchTransportWithStartedCity(start);
        String resultString = new String();

        for (Transport transport : result.keySet()) {
            for (Trajet trajet : transport.getJourney()) {
                if (trajet.getStart().name().equals(start.name())
                        && trajet.getFreeCapacity() > 0) {
                    resultString += "ID : " + transport.getIdTransport() + "\nType : "
                    + transport.getType() + "\nTrajets :\n\t"
                    + trajet.toString() + "\n\t";
                }
            }
        }
        
        System.out.println(resultString);
    }
    
    /**
     * Effectuer et afficher une recherche des transports allant vers une ville
     * donnée
     * 
     * @param arrived
     */
    public void searchArrived(Ville arrived) {
        HashMap<Transport, Trajet> result = searchTransportWithArrivedCity(arrived);
        String resultString = new String();

        for (Transport transport : result.keySet()) {
            for (Trajet trajet : transport.getJourney()) {
                if (trajet.getArrived().name().equals(arrived.name())
                        && trajet.getFreeCapacity() > 0) {
                    resultString += "ID : " + transport.getIdTransport() + "\nType : "
                    + transport.getType() + "\nTrajets :\n\t"
                    + trajet.toString() + "\n\t";
                }
            }
        }
        
        System.out.println(resultString);
    }

    /**
     * Ajouter une réservation au trajet correspondant
     * 
     * @param idTrajet
     * @param prénom
     * @param nom
     * @return boolean
     */
    public boolean addReservation(int idTrajet, String prénom, String nom) {
        try {
            Trajet tr = getTrajet(idTrajet);
            Voyageur v = new Voyageur(prénom, nom);
            if (tr.addReservation(v)) {
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return false;
        }

    }

    /**
     * Supprimer le transport correspondant à l'identifiant
     * 
     * @param idTransport
     */
    public boolean deleteTransport(int idTransport) {
        try {
            Transport t = getTransport(idTransport);
            return this.transports.remove(t);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return false;
        }
    }

    /**
     * Suprimer le trajet correspondant à l'identifant
     * 
     * @param idTrajet
     */
    public boolean deleteTrajet(int idTrajet) {
        try {
            int idTransport = getIdTransportOfTrajet(idTrajet);
            Transport t = getTransport(idTransport);
            return t.deleteJourney(idTrajet);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return false;
        }
    }

    /**
     * Obenir le trajet correspondant à l'identifiant
     * 
     * @param idTrajet
     * @return Trajet
     * @throws Exception
     */
    private Trajet getTrajet(int idTrajet) throws Exception {
        for (Transport t : transports) {
            for (Trajet tra : t.getJourney()) {
                if (tra.getIdTrajet() == idTrajet)
                    return tra;
            }
        }
        throw new Exception("Invlaid idTrajet");
    }

    /**
     * Obtenir le transport correspondant à l'identifiant
     * 
     * @param idTransport
     * @return Transport
     * @throws Exception
     */
    private Transport getTransport(int idTransport) throws Exception {
        for (Transport t : transports) {
            if (t.getIdTransport() == idTransport) {
                return t;
            }
        }
        throw new Exception("Invalid idTransport");
    }

    /**
     * Obtenir l'identifiant du tranport possédant le trajet correspondant à
     * l'identifiant donné.
     * 
     * @param idTrajet
     * @return
     * @throws Exception
     */
    private int getIdTransportOfTrajet(int idTrajet) throws Exception {
        for (Transport t : transports) {
            for (Trajet tra : t.getJourney()) {
                if (tra.getIdTrajet() == idTrajet) {
                    return t.getIdTransport();
                }
            }
        }
        throw new Exception("Invalid idTrajet");
    }

}
